/*
    This file is part of Corrade.

    Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
                2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025
              Vladimír Vondruš <mosra@centrum.cz>

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.
*/

#include "Debug.h"

#include <cstdlib>
#include <cstdint>
#include <iostream>
#include <iomanip>
#include <sstream>

/* For isatty() on Unix-like systems */
#ifdef CORRADE_TARGET_UNIX
#include <unistd.h>

/* Node.js alternative to isatty() on Emscripten */
#elif defined(CORRADE_TARGET_EMSCRIPTEN)
#include <emscripten.h>

#elif defined(CORRADE_TARGET_WINDOWS)
#define WIN32_LEAN_AND_MEAN 1
#define VC_EXTRALEAN

/* For isatty() on Windows. Needed by both ANSI and non-ANSI color printing. */
#include <io.h>

/* WINAPI-based colored output on Windows */
#ifndef CORRADE_UTILITY_USE_ANSI_COLORS
#include <windows.h>
#include <wincon.h>
#endif
#endif

#include "Corrade/Containers/GrowableArray.h"
#include "Corrade/Containers/EnumSet.hpp"
#include "Corrade/Containers/String.h"
#include "Corrade/Containers/StringView.h"
#include "Corrade/Utility/DebugStl.h"
#include "Corrade/Utility/Macros.h" /* CORRADE_THREAD_LOCAL */

#if defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_BUILD_STATIC_UNIQUE_GLOBALS) && !defined(CORRADE_TARGET_WINDOWS_RT)
#include "Corrade/Utility/Implementation/WindowsWeakSymbol.h"
#endif

#ifdef CORRADE_SOURCE_LOCATION_BUILTINS_SUPPORTED
#include "Corrade/Utility/Assert.h"
#endif

#ifdef CORRADE_TARGET_EMSCRIPTEN
/* Implemented in Utility.js.in */
extern "C" {
    bool corradeUtilityIsTty(int output);
}
#endif

namespace Corrade { namespace Utility {

namespace {

template<class T> inline void toStream(std::ostream& s, const T& value) {
    s << value;
}

template<> inline void toStream(std::ostream& s, const Containers::StringView& value) {
    s.write(value.data(), value.size());
}

template<> inline void toStream(std::ostream& s, const Containers::MutableStringView& value) {
    s.write(value.data(), value.size());
}

template<> inline void toStream(std::ostream& s, const Containers::String& value) {
    s.write(value.data(), value.size());
}

template<> inline void toStream<Implementation::DebugOstreamFallback>(std::ostream& s, const Implementation::DebugOstreamFallback& value) {
    value.apply(s);
}

#if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_UTILITY_USE_ANSI_COLORS)
HANDLE streamOutputHandle(const std::ostream* s) {
    /* The isatty() is there to detect if the output is redirected to a file.
       If it would be, GetStdHandle() returns a valid handle, but subsequent
       calls to GetConsoleScreenBufferInfo() or SetConsoleTextAttribute() would
       fail because "The handle must have the GENERIC_READ access right.", yes,
       a READ access for an OUTPUT handle, a very helpful documentation and
       basically no possibility of being able to find any further info on this
       except for contemplating my life decisions while staring into a blank
       white door while taking a long dump on a toilet.

       I wouldn't care or bother (and I didn't for the past six or so years
       since the original implementation of this was gifted to me), but having
       an error generated by a debug output printer is somewhat problematic
       when the thing being printed is the value of GetLastError(), such as in
       Implementation::printWindowsErrorString(). Yes, so you always get
       "error 6 (The handle is invalid.)" if CORRADE_UTILITY_USE_ANSI_COLORS
       is not set.

       After having confirmed a suspicion that "GENERIC_READ access right"
       means "not being redirected", the next step was to find a Windows API
       that tells me such a fact. Apparently, the most direct Windows-native™
       way to check this is by calling GetFileInformationByHandle() on the
       handle and ... seeing if it produces an error. Amazing, wonderful. Back
       to the start. Fortunately, Windows is Still A Bit Of Unix At Heart, so
       I just ask isatty(), like I do elsewhere.

       Actually, no, because then I would have to
        #pragma warning(disable: 4996)
       and also
        #pragma clang diagnostic ignored "-Wdeprecated-declarations"
       for clang-cl like I do below for the ANSI build and that's just too much
       already. So I "use the ISO C and C++ conformant name: _isatty" As The
       Computer God Demands. */
    return s == &std::cout && _isatty(1) ? GetStdHandle(STD_OUTPUT_HANDLE) :
           s == &std::cerr && _isatty(2) ? GetStdHandle(STD_ERROR_HANDLE) :
           INVALID_HANDLE_VALUE;
}
#endif

}

#if !defined(CORRADE_BUILD_STATIC_UNIQUE_GLOBALS) || defined(CORRADE_TARGET_WINDOWS)
/* (Of course) can't be in an unnamed namespace in order to export it below
   (except for Windows, where we do extern "C" so this doesn't matter, but we
   don't want to expose the DebugGlobals symbols if not needed) */
namespace {
#endif

struct DebugGlobals {
    std::ostream *output, *warningOutput, *errorOutput;
    #if !defined(CORRADE_TARGET_WINDOWS) ||defined(CORRADE_UTILITY_USE_ANSI_COLORS)
    Debug::Color color;
    bool colorBold, colorInverted;
    #endif
};

#ifdef CORRADE_BUILD_MULTITHREADED
CORRADE_THREAD_LOCAL
#endif
#if defined(CORRADE_BUILD_STATIC_UNIQUE_GLOBALS) && !defined(CORRADE_TARGET_WINDOWS)
/* On static builds that get linked to multiple shared libraries and then used
   in a single app we want to ensure there's just one global symbol. On Linux
   it's apparently enough to just export, macOS needs the weak attribute.
   Windows handled differently below. */
CORRADE_VISIBILITY_EXPORT
    #ifdef CORRADE_TARGET_GCC
    __attribute__((weak))
    #else
    /* uh oh? the test will fail, probably */
    #endif
#endif
DebugGlobals debugGlobals{
    #if defined(CORRADE_TARGET_MINGW) && defined(CORRADE_TARGET_CLANG)
    /* Referencing the globals directly makes MinGW Clang segfault for some reason */
    Debug::defaultOutput(), Warning::defaultOutput(), Error::defaultOutput(),
    #else
    &std::cout, &std::cerr, &std::cerr,
    #endif
    #if !defined(CORRADE_TARGET_WINDOWS) ||defined(CORRADE_UTILITY_USE_ANSI_COLORS)
    Debug::Color::Default, false, false
    #endif
};

#if !defined(CORRADE_BUILD_STATIC_UNIQUE_GLOBALS) || defined(CORRADE_TARGET_WINDOWS)
}
#endif

/* Windows can't have a symbol both thread-local and exported, moreover there
   isn't any concept of weak symbols. Exporting thread-local symbols can be
   worked around by exporting a function that then returns a reference to a
   non-exported thread-local symbol; and finally GetProcAddress() on
   GetModuleHandle(nullptr) "emulates" the weak linking as it's guaranteed to
   pick up the same symbol of the final exe independently of the DLL it was
   called from. To avoid #ifdef hell in code below, the debugGlobals are
   redefined to return a value from this uniqueness-ensuring function. */
#if defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_BUILD_STATIC_UNIQUE_GLOBALS) && !defined(CORRADE_TARGET_WINDOWS_RT)
/* Clang-CL complains that the function has a return type incompatible with C.
   I don't care, I only need an unmangled name to look up later at runtime. */
#ifdef CORRADE_TARGET_CLANG_CL
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
#endif
extern "C" CORRADE_VISIBILITY_EXPORT DebugGlobals& corradeUtilityUniqueDebugGlobals();
extern "C" CORRADE_VISIBILITY_EXPORT DebugGlobals& corradeUtilityUniqueDebugGlobals() {
    return debugGlobals;
}
#ifdef CORRADE_TARGET_CLANG_CL
#pragma clang diagnostic pop
#endif

namespace {

DebugGlobals& windowsDebugGlobals() {
    /* A function-local static to ensure it's only initialized once without any
       race conditions among threads */
    static DebugGlobals&(*const uniqueGlobals)() = reinterpret_cast<DebugGlobals&(*)()>(Implementation::windowsWeakSymbol("corradeUtilityUniqueDebugGlobals", reinterpret_cast<void*>(&corradeUtilityUniqueDebugGlobals)));
    return uniqueGlobals();
}

}

#define debugGlobals windowsDebugGlobals()
#endif

/* Used by the Debug(Containers::String*) constructors */
class StringStream: public std::ostream, std::streambuf {
    public:
        /* Not doing anything on construction -- in 99% cases, _out will be
           empty initially, so overflow() has to be called anyway, and there it
           takes care of everything */
        explicit StringStream(Containers::String& out): std::ostream{static_cast<std::streambuf*>(this)}, _out(out) {}

        /* When destructing the stream, be sure to sync. Without this, debug
           output with NoNewlineAtTheEnd wouldn't use std::endl and would never
           sync on its own. */
        ~StringStream() override {
            sync();
        }

    private:
        int sync() override {
            /* If sync() gets called multiple times without any overflow() in
               between, the data are owned by the string, not the array, and
               the remaining capacity is artificially set to 0 in a setp() call
               to prevent it from being appended to. Exit early in that case as
               there's nothing left to be done. */
            if(!_data)
                return 0;

            /* Resize to just what had been written, add a null terminator at
               the end */
            /** @todo clean up once the string is capable of growing */
            arrayResize(_data, NoInit, _initialSize + pptr() - pbase());
            arrayAppend(_data, '\0');

            /* The above might have reallocated, for example when the _data had
               no space for the null terminator. Call setp() to update the
               pointers, but don't leave any extra capacity to force overflow()
               to be called for next append, which then adopts the string back
               to the array. Otherwise it would be impossible to distinguish
               whether the string was modified by the stream (causing pptr() to
               change) or from outside (causing pptr() to point to an outdated
               location). */
            _initialSize = _data.size() - 1;
            setp(_data.end() - 1, _data.end() - 1);

            /* Move the array guts back to the string, making it one byte
               smaller as the last byte is the null terminator */
            const Containers::Array<char>::Deleter deleter = _data.deleter();
            _out = Containers::String{_data.release(), _initialSize, deleter};

            return 0;
        }

        std::streambuf::int_type overflow(std::streambuf::int_type ch) override {
            /* Not sure when this could happen, and the STL docs aren't helpful
               either. Plus, which madman did this comparison?! */
            CORRADE_INTERNAL_DEBUG_ASSERT(!std::char_traits<char>::eq_int_type(ch, std::char_traits<char>::eof()));

            /* If overflow() was not called yet or sync() got called right
               before (for example from a Debug instance being destructed in
               nested scope), the data are owned by the string. Transfer them
               to the array. */
            if(!_data) {
                /* If the string is a SSO (i.e., or also empty), the conversion
                   would allocate, which we would immediately replace with a
                   growing allocation, so just put it into a growing allocation
                   already. It's next to impossible to verify that the
                   unnecessary extra allocation indeed wasn't done, though, so
                   the test for this branch relies on `operator Array&&`
                   clearing the contents.

                   The string can also become a SSO subsequently, not only for
                   the very first time, for example when it's emptied /
                   replaced by the caller. */
                if(_out.isSmall())
                    arrayAppend(_data, _out);
                else
                    _data = Utility::move(_out);

                /* All that's in the array should now be preserved, appending
                   only after. Call setp() to update the pointer to reflect
                   the data array. If this is called after sync() with the
                   string not being touched in meanwhile, the setp() sets the
                   same values as before. */
                _initialSize = _data.size();
                setp(_data.begin() + _initialSize, _data.end());
            }

            /* Append the overflowing char, which in turn may grow the capacity
               in some way. */
            arrayAppend(_data, char(ch));
            /* ASan container annotations would complain if the stream would
               write to the capacity, so resize it to match the capacity. The
               actual end pointer is then queried only when converting to a
               string at the end. */
            arrayResize(_data, NoInit, arrayCapacity(_data));

            /* The above might have reallocated, call setp() to update the
               pointer for next insertion. Again, all that has been written so
               far including the one overflowing char, is meant to be
               preserved, appending only after. */
            _initialSize += pptr() - pbase() + 1;
            setp(_data.begin() + _initialSize, _data.end());

            /* The docs say it should return a non-EOF value on success. Is 0 a
               non-EOF value?! I have no idea. I'll just return what I
               received, so if that's an EOF, so be it. */
            return ch;
        }

        /** @todo use just the string once it's capable of growing */
        Containers::Array<char> _data;
        Containers::String& _out;
        std::size_t _initialSize;
};

enum class Debug::InternalFlag: unsigned char {
    OwnedStream = 1 << 0,
    ValueWritten = 1 << 1,
    ColorWritten = 1 << 2,
    #if !defined(CORRADE_TARGET_WINDOWS) || defined(CORRADE_UTILITY_USE_ANSI_COLORS)
    PreviousColorBold = 1 << 3,
    PreviousColorInverted = 1 << 4
    #endif
};

template<Debug::Color c, bool bold> Debug::Modifier Debug::colorInternal() {
    return [](Debug& debug) {
        if(!debug._output || (debug._flags & Flag::DisableColors))
            return;

        debug._internalFlags |= InternalFlag::ColorWritten|InternalFlag::ValueWritten;
        #if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_UTILITY_USE_ANSI_COLORS)
        HANDLE h = streamOutputHandle(debug._output);
        if(h != INVALID_HANDLE_VALUE) SetConsoleTextAttribute(h,
            (debug._previousColorAttributes & ~(FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED|FOREGROUND_INTENSITY)) |
            char(c) |
            (bold ? FOREGROUND_INTENSITY : 0));
        #else
        debugGlobals.color = c;
        debugGlobals.colorBold = bold;
        debugGlobals.colorInverted = false;
        if(bold) {
            /* Adds an extra reset to also undo the inverse, if was set
               before */
            constexpr const char code[]{'\033', '[', '0', ';', '1' , ';', '3', '0' + char(c), 'm', '\0'};
            *debug._output << code;
        } else {
            /* This resets both bold and inverse */
            constexpr const char code[]{'\033', '[', '0', ';', '3', '0' + char(c), 'm', '\0'};
            *debug._output << code;
        }
        #endif
    };
}

#if !defined(CORRADE_TARGET_WINDOWS) || defined(CORRADE_UTILITY_USE_ANSI_COLORS)
template<Debug::Color c> Debug::Modifier Debug::invertedColorInternal() {
    return [](Debug& debug) {
        if(!debug._output || (debug._flags & Flag::DisableColors))
            return;

        debug._internalFlags |= InternalFlag::ColorWritten|InternalFlag::ValueWritten;
        debugGlobals.color = c;
        debugGlobals.colorBold = false;
        debugGlobals.colorInverted = true;
        /* Adds an extra reset to also undo the bold, if was set before */
        constexpr const char code[] = { '\033', '[', '0', ';', '7', ';', '3', '0' + char(c), 'm', '\0' };
        *debug._output << code;
    };
}
#endif

inline void Debug::resetColorInternal() {
    if(!_output || !(_internalFlags & InternalFlag::ColorWritten))
        return;

    _internalFlags &= ~InternalFlag::ColorWritten;
    _internalFlags |= InternalFlag::ValueWritten;
    #if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_UTILITY_USE_ANSI_COLORS)
    HANDLE h = streamOutputHandle(_output);
    if(h != INVALID_HANDLE_VALUE)
        SetConsoleTextAttribute(h, _previousColorAttributes);
    #else
    if(_internalFlags >= InternalFlag::PreviousColorBold || _internalFlags >= InternalFlag::PreviousColorInverted) {
        /* Only one of the two should be set by our code */
        CORRADE_INTERNAL_ASSERT(!(_internalFlags >= InternalFlag::PreviousColorBold) || !(_internalFlags >= InternalFlag::PreviousColorInverted));
        const char code[]{'\033', '[', '0', ';', _internalFlags >= InternalFlag::PreviousColorBold ? '1' : '7', ';', '3', char('0' + char(_previousColor)), 'm', '\0'};
        *_output << code;
    } else if(_previousColor != Color::Default) {
        const char code[]{'\033', '[', '0', ';', '3', char('0' + char(_previousColor)), 'm', '\0'};
        *_output << code;
    } else *_output << "\033[0m";

    debugGlobals.color = _previousColor;
    debugGlobals.colorBold = _internalFlags >= InternalFlag::PreviousColorBold;
    debugGlobals.colorInverted = _internalFlags >= InternalFlag::PreviousColorInverted;
    #endif
}

auto Debug::color(Color color) -> Modifier {
    /* Crazy but working solution to work around the need for capturing lambda
       which disallows converting it to function pointer */
    switch(color) {
        #define _c(color) case Color::color: return colorInternal<Color::color, false>();
        _c(Black)
        _c(Red)
        _c(Green)
        _c(Yellow)
        _c(Blue)
        _c(Magenta)
        _c(Cyan)
        _c(White)
        #if !defined(CORRADE_TARGET_WINDOWS) || defined(CORRADE_UTILITY_USE_ANSI_COLORS)
        _c(Default)
        #endif
        #undef _c
    }

    CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}

auto Debug::boldColor(Color color) -> Modifier {
    /* Crazy but working solution to work around the need for capturing lambda
       which disallows converting it to function pointer */
    switch(color) {
        #define _c(color) case Color::color: return colorInternal<Color::color, true>();
        _c(Black)
        _c(Red)
        _c(Green)
        _c(Yellow)
        _c(Blue)
        _c(Magenta)
        _c(Cyan)
        _c(White)
        #if !defined(CORRADE_TARGET_WINDOWS) || defined(CORRADE_UTILITY_USE_ANSI_COLORS)
        _c(Default)
        #endif
        #undef _c
    }

    CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}

#if !defined(CORRADE_TARGET_WINDOWS) || defined(CORRADE_UTILITY_USE_ANSI_COLORS)
auto Debug::invertedColor(Color color) -> Modifier {
    /* Crazy but working solution to work around the need for capturing lambda
       which disallows converting it to function pointer */
    switch(color) {
        #define _c(color) case Color::color: return invertedColorInternal<Color::color>();
        _c(Black)
        _c(Red)
        _c(Green)
        _c(Yellow)
        _c(Blue)
        _c(Magenta)
        _c(Cyan)
        _c(White)
        _c(Default)
        #undef _c
    }

    CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
#endif

void Debug::resetColor(Debug& debug) {
    debug.resetColorInternal();
}

std::ostream* Debug::defaultOutput() { return &std::cout; }
std::ostream* Warning::defaultOutput() { return &std::cerr; }
std::ostream* Error::defaultOutput() { return &std::cerr; }

std::ostream* Debug::output() { return debugGlobals.output; }
std::ostream* Warning::output() { return debugGlobals.warningOutput; }
std::ostream* Error::output() { return debugGlobals.errorOutput; }

bool Debug::isTty(std::ostream* const output) {
    /* On Windows with WINAPI colors check the stream output handle */
    #if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_UTILITY_USE_ANSI_COLORS)
    return streamOutputHandle(output) != INVALID_HANDLE_VALUE;

    /* We can autodetect via isatty() on Unix-like systems and Windows with
       ANSI colors enabled */
    #elif (defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_UTILITY_USE_ANSI_COLORS)) || defined(CORRADE_TARGET_UNIX)
    return
        /* Windows RT projects have C4996 treated as error by default. WHY.
           Also, clang-cl doesn't understand warning IDs yet, so using its own
           warning suppression right now. */
        #ifdef CORRADE_TARGET_CLANG_CL
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        #elif defined(CORRADE_TARGET_MSVC)
        #pragma warning(push)
        #pragma warning(disable: 4996)
        #endif
        ((output == &std::cout && isatty(1)) ||
         (output == &std::cerr && isatty(2)))
        #ifdef CORRADE_TARGET_CLANG_CL
        #pragma clang diagnostic pop
        #elif defined(CORRADE_TARGET_MSVC)
        #pragma warning(pop)
        #endif
        #ifdef CORRADE_TARGET_APPLE
        /* Xcode's console reports that it is a TTY, but it doesn't support
           colors. Originally this was testing for XPC_SERVICE_NAME being
           defined because that's always defined when running inside Xcode, but
           nowadays that's often defined also outside of Xcode, so it's
           useless. According to https://stackoverflow.com/a/39292112, we can
           reliably check for TERM -- if it's null, we're inside Xcode. */
        && std::getenv("TERM")
        #endif
        ;

    /* Emscripten isatty() is broken since forever, so we have to call into
       Node.js: https://github.com/kripken/emscripten/issues/4920 */
    #elif defined(CORRADE_TARGET_EMSCRIPTEN)
    int out = 0;
    if(output == &std::cout)
        out = 1;
    else if(output == &std::cerr)
        out = 2;
    return corradeUtilityIsTty(out);

    /* Otherwise can't be autodetected, thus disable colkors by default */
    #else
    return false;
    #endif
}

bool Debug::isTty() { return isTty(debugGlobals.output); }
bool Warning::isTty() { return Debug::isTty(debugGlobals.warningOutput); }
bool Error::isTty() { return Debug::isTty(debugGlobals.errorOutput); }

Debug::Debug(std::ostream* const output, const Flags flags): _flags{flags}, _immediateFlags{Flag::NoSpace} {
    /* Save previous global output and replace it with current one */
    _previousGlobalOutput = debugGlobals.output;
    debugGlobals.output = _output = output;

    /* Save previous global color */
    #if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_UTILITY_USE_ANSI_COLORS)
    HANDLE h = streamOutputHandle(_output);
    if(h != INVALID_HANDLE_VALUE) {
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        GetConsoleScreenBufferInfo(h, &csbi);
        _previousColorAttributes = csbi.wAttributes;
    }
    #else
    _previousColor = debugGlobals.color;
    if(debugGlobals.colorBold)
        _internalFlags |= InternalFlag::PreviousColorBold;
    if(debugGlobals.colorInverted)
        _internalFlags |= InternalFlag::PreviousColorInverted;
    #endif
}

/* Yeah, this is a naked allocation, I know. Debug::cleanupOnDestruction() then
   frees it. Fingers crossed that this is exception safe. */
Debug::Debug(Containers::String* const output, const Flags flags): Debug{output ? new StringStream{*output} : nullptr, flags} {
    _internalFlags |= InternalFlag::OwnedStream;
}

#ifdef CORRADE_SOURCE_LOCATION_BUILTINS_SUPPORTED
namespace Implementation {

DebugSourceLocation::DebugSourceLocation(Debug&& debug, const char* file, int line): debug{&debug} {
    debug._sourceLocationFile = file;
    debug._sourceLocationLine = line;
}

}
#endif

Warning::Warning(std::ostream* const output, const Flags flags): Debug{flags} {
    /* Save previous global output and replace it with current one */
    _previousGlobalWarningOutput = debugGlobals.warningOutput;
    debugGlobals.warningOutput = _output = output;
}

Warning::Warning(Containers::String* const output, const Flags flags): Debug{flags} {
    /* Save previous global output and replace it with current one */
    _previousGlobalWarningOutput = debugGlobals.warningOutput;
    /** @todo any better idea how to do these two lines without duplicating
        them in Debug, Warning and Error? */
    debugGlobals.warningOutput = _output = output ? new StringStream{*output} : nullptr;
    _internalFlags |= InternalFlag::OwnedStream;
}

Error::Error(std::ostream* const output, const Flags flags): Debug{flags} {
    /* Save previous global output and replace it with current one */
    _previousGlobalErrorOutput = debugGlobals.errorOutput;
    debugGlobals.errorOutput = _output = output;
}

Error::Error(Containers::String* const output, const Flags flags): Debug{flags} {
    /* Save previous global output and replace it with current one */
    _previousGlobalErrorOutput = debugGlobals.errorOutput;
    /** @todo any better idea how to do these two lines without duplicating
        them in Debug, Warning and Error? */
    debugGlobals.errorOutput = _output = output ? new StringStream{*output} : nullptr;
    _internalFlags |= InternalFlag::OwnedStream;
}

Debug::Debug(const Flags flags): Debug{debugGlobals.output, flags} {}
Warning::Warning(const Flags flags): Warning{debugGlobals.warningOutput, flags} {}
Error::Error(const Flags flags): Error{debugGlobals.errorOutput, flags} {}

void Debug::cleanupOnDestruction() {
    #ifdef CORRADE_SOURCE_LOCATION_BUILTINS_SUPPORTED
    /* Print source location if not printed yet -- this means saying a
       !Debug{}; will print just that, while Debug{}; is a no-op */
    if(_output && _sourceLocationFile) {
        CORRADE_INTERNAL_ASSERT(_immediateFlags & Flag::NoSpace);
        *_output << _sourceLocationFile << ":" << _sourceLocationLine;
        _internalFlags |= InternalFlag::ValueWritten;
    }
    #endif

    /* Reset output color */
    resetColorInternal();

    /* Newline at the end */
    if(_output && (_internalFlags & InternalFlag::ValueWritten) && !(_flags & Flag::NoNewlineAtTheEnd))
        *_output << std::endl;

    /* Reset previous global output */
    debugGlobals.output = _previousGlobalOutput;

    /* If the stream was owned (i.e., a StringStream), delete it */
    if(_internalFlags >= InternalFlag::OwnedStream)
        delete _output;
}

Debug::~Debug() {
    cleanupOnDestruction();
}

Warning::~Warning() {
    debugGlobals.warningOutput = _previousGlobalWarningOutput;
}

void Error::cleanupOnDestruction() {
    debugGlobals.errorOutput = _previousGlobalErrorOutput;
}

Error::~Error() {
    cleanupOnDestruction();
}

/* MSVC in a Release build complains that "destructor never returns, potential
   memory leak". Well, yes, since this is a [[noreturn]] function. */
#if defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG_CL)
#pragma warning(push)
#pragma warning(disable: 4722)
#endif
Fatal::~Fatal() {
    /* Manually call cleanup of Error and Debug superclasses because their
       destructor will never be called */
    Error::cleanupOnDestruction();
    Debug::cleanupOnDestruction();

    std::exit(_exitCode);
}
#if defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG_CL)
#pragma warning(pop)
#endif

template<class T> Debug& Debug::print(const T& value) {
    if(!_output) return *this;

    #ifdef CORRADE_SOURCE_LOCATION_BUILTINS_SUPPORTED
    /* Print source location, if not printed yet */
    if(_sourceLocationFile) {
        CORRADE_INTERNAL_ASSERT(_immediateFlags & Flag::NoSpace);
        *_output << _sourceLocationFile << ":" << _sourceLocationLine << ": ";
        _sourceLocationFile = nullptr;
    }
    #endif

    /* Separate values with spaces if enabled */
    if(!((_immediateFlags|_flags) & Flag::NoSpace))
        *_output << ' ';
    /* Print the next value as hexadecimal if enabled */
    /** @todo this does strange crap for negative values (printing them as
        unsigned), revisit once iostreams are not used anymore */
    if(((_immediateFlags|_flags) & Flag::Hex) && std::is_integral<T>::value)
        *_output << "0x" << std::hex;

    toStream(*_output, value);

    /* Reset the hexadecimal printing back if it was enabled */
    if(((_immediateFlags|_flags) & Flag::Hex) && std::is_integral<T>::value)
        *_output << std::dec;

    /* Reset all immediate flags after */
    _immediateFlags = {};

    _internalFlags |= InternalFlag::ValueWritten;
    return *this;
}

Debug& Debug::operator<<(const void* const value) {
    return *this << hex << reinterpret_cast<std::uintptr_t>(value);
}

Debug& Debug::operator<<(const char* value) { return print(value); }
Debug& Debug::operator<<(Containers::StringView value) { return print(value); }
Debug& Debug::operator<<(Containers::MutableStringView value) { return print(value); }
Debug& Debug::operator<<(const Containers::String& value) { return print(value); }

Debug& Debug::operator<<(bool value) { return print(value ? "true" : "false"); }
Debug& Debug::operator<<(int value) { return print(value); }

Debug& Debug::operator<<(char value) { return print(int(value)); }

Debug& Debug::operator<<(unsigned char value) {
    /* Convert to int to avoid infinite recursion to operator<<(unsigned char) */
    const int v = value;

    /* Print the value as a shade of gray */
    if((_immediateFlags|_flags) & Flag::Color) {
        const char* shade;
        if(value < 51)       shade = "  ";
        else if(value < 102) shade = "░░";
        else if(value < 153) shade = "▒▒";
        else if(value < 204) shade = "▓▓";
        else                 shade = "██";

        /* If ANSI colors are disabled, use just the shade */
        if((_immediateFlags|_flags) & Flag::DisableColors)
            return print(shade);
        else {
            print("\033[38;2;");

            /* Disable space between values for everything after the initial
               value */
            const Flags previousFlags = _flags;
            _flags = previousFlags|Flag::NoSpace;

            /* Set both background and foreground, reset back after */
            *this << v << ";" << v << ";" << v << "m\033[48;2;"
                << v << ";" << v << ";" << v << "m" << shade << "\033[0m";

            /* Reset original flags */
            _flags = previousFlags;
            return *this;
        }

    /* Otherwise print its numeric value */
    } else return print(v);
}

Debug& Debug::operator<<(long value) { return print(value); }
Debug& Debug::operator<<(long long value) { return print(value); }
Debug& Debug::operator<<(unsigned value) { return print(value); }
Debug& Debug::operator<<(unsigned long value) { return print(value); }
Debug& Debug::operator<<(unsigned long long value) { return print(value); }

Debug& Debug::operator<<(float value) {
    if(!_output) return *this;
    *_output << std::setprecision(Implementation::FloatPrecision<float>::Digits);
    return print(value);
}
Debug& Debug::operator<<(double value) {
    if(!_output) return *this;
    *_output << std::setprecision(Implementation::FloatPrecision<double>::Digits);
    return print(value);
}
Debug& Debug::operator<<(long double value) {
    if(!_output) return *this;
    *_output << std::setprecision(Implementation::FloatPrecision<long double>::Digits);
    return print(value);
}

Debug& Debug::operator<<(char32_t value) {
    std::ostringstream o;
    o << "U+" << std::hex << std::uppercase << std::setw(4) << std::setfill('0') << std::uint32_t(value);
    return print(o.str());
}

Debug& Debug::operator<<(const char32_t* value) {
    return *this << std::u32string(value);
}

Debug& Debug::operator<<(std::nullptr_t) {
    return print("nullptr");
}

Debug& operator<<(Debug& debug, Debug::Color value) {
    switch(value) {
        /* LCOV_EXCL_START */
        #define _c(value) case Debug::Color::value: return debug << "Utility::Debug::Color::" #value;
        _c(Black)
        _c(Red)
        _c(Green)
        _c(Yellow)
        _c(Blue)
        _c(Magenta)
        _c(Cyan)
        _c(White)
        #if !defined(CORRADE_TARGET_WINDOWS) || defined(CORRADE_UTILITY_USE_ANSI_COLORS)
        _c(Default) /* Alias to White on Windows */
        #endif
        #undef _c
        /* LCOV_EXCL_STOP */
    }

    return debug << "Utility::Debug::Color(" << Debug::nospace << Debug::hex << static_cast<unsigned char>(char(value)) << Debug::nospace << ")";
}

Debug& operator<<(Debug& debug, Debug::Flag value) {
    switch(value) {
        /* LCOV_EXCL_START */
        #define _c(value) case Debug::Flag::value: return debug << "Utility::Debug::Flag::" #value;
        _c(NoNewlineAtTheEnd)
        _c(DisableColors)
        _c(NoSpace)
        _c(Packed)
        _c(Color)
        /* Space reserved for Bin and Oct */
        _c(Hex)
        #undef _c
        /* LCOV_EXCL_STOP */
    }

    return debug << "Utility::Debug::Flag(" << Debug::nospace << Debug::hex << static_cast<unsigned char>(value) << Debug::nospace << ")";
}

Debug& operator<<(Debug& debug, Debug::Flags value) {
    return Containers::enumSetDebugOutput(debug, value, "Utility::Debug::Flags{}", {
        Debug::Flag::NoNewlineAtTheEnd,
        Debug::Flag::DisableColors,
        Debug::Flag::NoSpace,
        Debug::Flag::Packed,
        Debug::Flag::Color,
        /* Space reserved for Bin and Oct */
        Debug::Flag::Hex});
}

/** @todo when we get rid of iostreams, make this inline in DebugStl.h so we
    don't bloat our binaries with STL symbols */
Debug& Implementation::debugPrintStlString(Debug& debug, const std::string& value) {
    return debug.print(value);
}

Debug& operator<<(Debug& debug, Implementation::DebugOstreamFallback&& value) {
    return debug.print(value);
}

}}
